#include <buola/gui/x11/auto.h>

#include <buola/gui/cicon.h>
#include <buola/gui/cwindow.h>
#include <buola/image/cimage.h>

#include <X11/Xatom.h>
#include <X11/Xutil.h>
#include <unistd.h>

#include <buola/gui/x11/cx11display.h>
#include <buola/gui/x11/cx11window.h>
#include <buola/gui/x11/cx11windowwm.h>
#include <buola/gui/x11/cx11toolkit.h>
#include <buola/gui/x11/icccm.h>
#include <buola/gui/x11/cx11dispatcher.h>

namespace buola { namespace gui { namespace x11 {

/////////////////////// constructor and destructor /////////////////

CX11WindowWM::CX11WindowWM(CX11Window *pWindow)
    :   mSyncCounter(0)
    ,   mWindow(pWindow)
{
    mWindow=pWindow;
    mDisplay=mWindow->GetDisplay();
    
    mState=EWindowState::NONE;

    mMapped=false;

    mBaseSize=CSize_d(nUndefined);
    mResizeInc=CSize_d(nUndefined);

    mIcon=nullptr;

    mSyncRequest=0;
}

CX11WindowWM::~CX11WindowWM()
{
}

////////////////////////////////////////////////////////////////
//                                                            //
//                          Interface                         //
//                                                            //
////////////////////////////////////////////////////////////////

void CX11WindowWM::Iconify(bool pB)
{
    if(pB)
    {
        if(mState&EWindowState::ICONIC)
            return;

        mState|=EWindowState::ICONIC;

        if(mMapped)
        {
            SendRootMessage(mDisplay->Atoms().mChangeState,IconicState);
        }
        else
        {
            ICCCMSetHints();
        }
    }
    else
    {
        if(!(mState&EWindowState::ICONIC))
            return;

        mState&=(~EWindowState::ICONIC);

        if(mMapped)
        {
            SendRootMessage(mDisplay->Atoms().mChangeState,NormalState);
        }
        else
        {
            ICCCMSetHints();
        }
    }
}

void CX11WindowWM::Maximize(EWindowState pMax)
{
    if(pMax==(mState&EWindowState::MAX))
        return;

    mState|=pMax;

    if(mMapped)
    {
        if(pMax==EWindowState::NONE)
        {
            SendRootMessage(mDisplay->Atoms().mNetWMState,0,
                            mDisplay->Atoms().mNetWMStateMaxVert,mDisplay->Atoms().mNetWMStateMaxHorz);
        }
        else if(pMax==EWindowState::MAX)
        {
            SendRootMessage(mDisplay->Atoms().mNetWMState,1,
                            mDisplay->Atoms().mNetWMStateMaxVert,mDisplay->Atoms().mNetWMStateMaxHorz);
        }
        else if(pMax==EWindowState::MAXVERT)
        {
            SendRootMessage(mDisplay->Atoms().mNetWMState,1,
                            mDisplay->Atoms().mNetWMStateMaxVert);
        }
        else if(pMax==EWindowState::MAXHORZ)
        {
            SendRootMessage(mDisplay->Atoms().mNetWMState,1,
                            mDisplay->Atoms().mNetWMStateMaxHorz);
        }
    }
    else
    {
        NETSetState();
    }
}

void CX11WindowWM::Shade(bool pB)
{
    if(pB)
    {
        if(mState&EWindowState::SHADED)
            return;

        mState|=EWindowState::SHADED;
    }
    else
    {
        if(!(mState&EWindowState::SHADED))
            return;

        mState&=(~EWindowState::SHADED);
    }

    if(mMapped)
    {
        SendRootMessage(mDisplay->Atoms().mNetWMState,pB?1:0,mDisplay->Atoms().mNetWMStateShaded);
    }
    else
    {
        NETSetState();
    }
}

void CX11WindowWM::Sticky(bool pB)
{
    if(mWindow)
    {
        if(pB)
        {
            if(mState&EWindowState::STICKY)
                return;

            mState|=EWindowState::STICKY;
        }
        else
        {
            if(!(mState&EWindowState::STICKY))
                return;

        mState&=(~EWindowState::STICKY);
        }
    }

    if(mMapped)
    {
        SendRootMessage(mDisplay->Atoms().mNetWMState,pB?1:0,
                        mDisplay->Atoms().mNetWMStateSticky);
    }
    else
    {
        NETSetState();
    }
}

void CX11WindowWM::SetDesktop(int)
{
}

void CX11WindowWM::FullScreen(bool pFull)
{
    if(pFull)
    {
        if(mState&EWindowState::FULLSCREEN) return;
        mState|=EWindowState::FULLSCREEN;
    }
    else
    {
        if(!(mState&EWindowState::FULLSCREEN)) return;
        mState&=(~EWindowState::FULLSCREEN);
    }

    if(mMapped)
    {
        SendRootMessage(mDisplay->Atoms().mNetWMState,pFull?1:0,
                        mDisplay->Atoms().mNetWMStateFullScreen);
    }
    else
    {
        NETSetState();
    }
}

void CX11WindowWM::Activate()
{
    SendRootMessage(mDisplay->Atoms().mNetActiveWindow,1);
}

void CX11WindowWM::StartMoveOperation(CMouseEvent *pE)
{
    XUngrabPointer(mDisplay->GetDisplay(),CurrentTime);
    ///\todo see how this affects the new grab system. maybe sending a fake buttonup event would work
//    get_toolkit()->RemoveGrabOfType(GRAB_BUTTON);
    SendRootMessage(mDisplay->Atoms().mNetWMMoveResize,
            (int)pE->mRootPoint.x,(int)pE->mRootPoint.y,
                _NET_WM_MOVERESIZE_MOVE,CX11Dispatcher::TranslateButtonToX(pE->mButton));
}

void CX11WindowWM::StartResizeOperation(CMouseEvent *pE,EDragState pType)
{
    int lType;
    switch((int)pType)
    {
    case (int)DRAG_WEST|(int)DRAG_NORTH:
        lType=_NET_WM_MOVERESIZE_SIZE_TOPLEFT;
        break;
    case DRAG_NORTH:
        lType=_NET_WM_MOVERESIZE_SIZE_TOP;
        break;
    case (int)DRAG_EAST|(int)DRAG_NORTH:
        lType=_NET_WM_MOVERESIZE_SIZE_TOPRIGHT;
        break;
    case DRAG_EAST:
        lType=_NET_WM_MOVERESIZE_SIZE_RIGHT;
        break;
    case (int)DRAG_EAST|(int)DRAG_SOUTH:
        lType=_NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT;
        break;
    case DRAG_SOUTH:
        lType=_NET_WM_MOVERESIZE_SIZE_BOTTOM;
        break;
    case (int)DRAG_WEST|(int)DRAG_SOUTH:
        lType=_NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT;
        break;
    case DRAG_WEST:
        lType=_NET_WM_MOVERESIZE_SIZE_LEFT;
        break;
    default:
        lType=_NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT;
        break;
    }

    XUngrabPointer(mDisplay->GetDisplay(),CurrentTime);
    ///\todo same as above
    //    get_toolkit()->RemoveGrabOfType(GRAB_BUTTON);
    SendRootMessage(mDisplay->Atoms().mNetWMMoveResize,
            (int)pE->mRootPoint.x,(int)pE->mRootPoint.y,lType,CX11Dispatcher::TranslateButtonToX(pE->mButton));
}

//////////////////// Properties, flags and style //////////////////

void CX11WindowWM::SetTitle(const std::wstring &pTitle)
{
    mWindow->SetWMProperty(mDisplay->Atoms().mName,pTitle);
    mWindow->SetWMProperty(mDisplay->Atoms().mIconName,pTitle);
    mWindow->SetWMProperty(mDisplay->Atoms().mNetWMName,pTitle,mDisplay->Atoms().mUTF8);
    mWindow->SetWMProperty(mDisplay->Atoms().mNetWMIconName,pTitle,mDisplay->Atoms().mUTF8);
}

void CX11WindowWM::SetIcon(PIcon pIcon)
{
    if(mIcon)
    {
        if(mIcon==pIcon) return;
    }

    mIcon=pIcon;

    img::CPicture lImage=mIcon->GetPixmap(48);
    if(!lImage)
    {
        throw XInternal(I18N("Can't get icon image in CX11WindowWM::SetIcon()"));
    }

    const int *lData=(const int*)lImage.RawBytes();
    if(!lData)
    {
        throw XInternal(I18N("Can't get raw image data in CX11WindowWM::SetIcon()"));
    }

    CSize_d lSize=lImage.Size();

    long int lIcon[2+(int)lSize.x*(int)lSize.y];

    lIcon[0]=(int)lSize.x;
    lIcon[1]=(int)lSize.y;
    for(int i=0;i<lSize.x*lSize.y;i++)
    {
        lIcon[i+2]=lData[i];
    }

    //TODO:fix this well
//  mWindow->SetWMProperty(mDisplay->Atoms().mNetWMIcon,lIcon,
//                                  2+(int)lSize.x*(int)lSize.y);
    mDisplay->SetWindowProperty(mWindow->GetX11ID(),mDisplay->Atoms().mNetWMIcon,XA_CARDINAL,
                                32,2+(int)lSize.x*(int)lSize.y,(unsigned char*)lIcon);
}

void CX11WindowWM::SetStrut(const CRect_d &pRect)
{
    int lStrut[4]={(int)pRect.l,(int)pRect.r,(int)pRect.t,(int)pRect.b};
    mWindow->SetWMProperty(mDisplay->Atoms().mNetWMStrut,lStrut,4);
}

void CX11WindowWM::SetOpacity(double pOpacity)
{
    if(pOpacity<0.0) pOpacity=0;
    if(pOpacity>1.0) pOpacity=1;

    unsigned int lOpacity=(unsigned int)(pOpacity*0xffffffff);

    mDisplay->SetWindowProperty(mWindow->GetX11ID(),mDisplay->Atoms().mNetWMWindowOpacity,
                XA_CARDINAL,32,1,&lOpacity);
}

////////////////////////////////////////////////////////////////
//                                                            //
//                          Internal                          //
//                                                            //
////////////////////////////////////////////////////////////////

//////////////////// Internal position & size ///////////////////

void CX11WindowWM::InternalMove(const CPoint_d &pPos)
{
    if(mMapped) return;
    mWindow->mPos=pPos;
    ICCCMSetSizeHints();
}

void CX11WindowWM::InternalResize(const CSize_d &pSize)
{
    if(mMapped) return;

    mWindow->mSize=pSize;
    ICCCMSetSizeHints();
}

void CX11WindowWM::InternalMoveResize(const CPoint_d &pPos,const CSize_d &pSize)
{
    if(mMapped) return;

    mWindow->mPos=pPos;
    mWindow->mSize=pSize;
    ICCCMSetSizeHints();
}

void CX11WindowWM::SyncRequested(int64_t pValue)
{
    mSyncRequest=pValue;
}

void CX11WindowWM::SyncFinished()
{
    mSyncCounter.Set(mSyncRequest);
    mSyncRequest=0;
}

//////////////////////// WM hints and properties //////////////////////

void CX11WindowWM::UpdateInfo()
{
    icccm::write_wm_protocols(mWindow->GetX11ID(),icccm::PROTOCOL_ALLBUOLA);
    icccm::write_wm_client_machine(mWindow->GetX11ID());
    icccm::write_wm_class(mWindow->GetX11ID());
    
    if(mWindow&&mWindow->GetWindow()->IsMainWindow())
        icccm::write_wm_command(mWindow->GetX11ID());

    icccm::write_net_wm_sync_request_counter(mWindow->GetX11ID(),(int)mSyncCounter.GetID());
    
    ICCCMSetHints();
    ICCCMSetSizeHints();
    NETSetState();
    if(mWindow)
        icccm::write_net_wm_window_type(mWindow->GetX11ID(),mWindow->GetWindow()->WindowType());
}

void CX11WindowWM::ICCCMSetHints()
{
    XWMHints *lHints=XAllocWMHints();

    lHints->input=mWindow->GetFlag(CX11Window::FLAG_DONTFOCUS)?false:true;
    lHints->initial_state=(mState&EWindowState::ICONIC)?IconicState:NormalState;
    lHints->flags=InputHint|StateHint;

    XSetWMHints(mDisplay->GetDisplay(),mWindow->GetX11ID(),lHints);

    XFree(lHints);

    if(!ShouldDecorate(mWindow->GetWindow().get()))
    {
        icccm::CMotifHints lMotifHints={MOTIF_DECORATIONS,0,0,0,0};

        mDisplay->SetWindowProperty(mWindow->GetX11ID(),mDisplay->Atoms().mMotifHints,
                mDisplay->Atoms().mMotifHints,32,5,(unsigned char*)&lMotifHints);
    }
}

void CX11WindowWM::ICCCMSetSizeHints()
{
    if(!mWindow) return;

    XSizeHints *lHints=XAllocSizeHints();
    lHints->flags=None;

///\todo implement this again
    #if 0
    if(!mWindow->GetRequestedPos().Undefined())
    {
        lHints->flags|=PPosition;
    }
    if(!mWindow->GetRequestedSize().Undefined())
    {
        lHints->flags|=PSize;
    }
    if(mWindow->GetSizeHints()&CWindow::HINT_FIXEDSIZE)
    {
        lHints->flags|=PMinSize|PMaxSize;
        lHints->min_width=(int)mWindow->GetRequestedSize().x;
        lHints->min_height=(int)mWindow->GetRequestedSize().y;
        lHints->max_width=(int)mWindow->GetRequestedSize().x;
        lHints->max_height=(int)mWindow->GetRequestedSize().y;
    }
    else
    {
/*
        if(!mWindow->GetMinClientSize().IsUndefined())
        {
            lHints->flags|=PMinSize;
            lHints->min_width=(int)(mWindow->GetMinSize().x+mWindow->GetRequestedSize().x-
                                            mWindow->GetRect().r);
            lHints->min_height=(int)(mWindow->GetMinSize().y+mWindow->GetRequestedSize().y-
                                            mWindow->GetRect().b);
        }
        if(!mWindow->GetMaxClientSize().IsUndefined())
        {
            lHints->flags|=PMaxSize;
            lHints->max_width=(int)(mWindow->GetMaxSize().x+mWindow->GetRequestedSize().x-
                                            mWindow->GetRect().r);
            lHints->max_height=(int)(mWindow->GetMaxSize().y+mWindow->GetRequestedSize().y-
                                            mWindow->GetRect().b);
        }
*/
        if(!mBaseSize.Undefined())
        {
            lHints->flags|=PBaseSize;
            lHints->base_width=(int)mBaseSize.x;
            lHints->base_height=(int)mBaseSize.y;
        }
        if(!mResizeInc.Undefined())
        {
            lHints->flags|=PResizeInc;
            lHints->width_inc=(int)mResizeInc.x;
            lHints->height_inc=(int)mResizeInc.y;
        }
    }
#endif
    XSetWMNormalHints(mDisplay->GetDisplay(),mWindow->GetX11ID(),lHints);

    XFree(lHints);
}

void CX11WindowWM::NETSetState()
{
    if(!mWindow) return;

    Atom lAtoms[10];
    int lNum=0;

    if(mWindow->GetWindow()->IsModal())
        lAtoms[lNum++]=mDisplay->Atoms().mNetWMStateModal;
    if(mState&EWindowState::STICKY)
        lAtoms[lNum++]=mDisplay->Atoms().mNetWMStateSticky;
    if(mState&EWindowState::MAXHORZ)
        lAtoms[lNum++]=mDisplay->Atoms().mNetWMStateMaxHorz;
    if(mState&EWindowState::MAXVERT)
        lAtoms[lNum++]=mDisplay->Atoms().mNetWMStateMaxVert;
    if(ShouldSkipTaskbar(mWindow->GetWindow().get()))
        lAtoms[lNum++]=mDisplay->Atoms().mNetWMStateSkipTaskbar;
    if(ShouldSkipPager(mWindow->GetWindow().get()))
        lAtoms[lNum++]=mDisplay->Atoms().mNetWMStateSkipPager;
    if(mState&EWindowState::ONTOP)
        lAtoms[lNum++]=mDisplay->Atoms().mNetWMStateAbove;
    if(mState&EWindowState::SHADED)
        lAtoms[lNum++]=mDisplay->Atoms().mNetWMStateShaded;
    if(mState&EWindowState::FULLSCREEN)
        lAtoms[lNum++]=mDisplay->Atoms().mNetWMStateFullScreen;

    mWindow->SetWMProperty(mDisplay->Atoms().mNetWMState,lAtoms,lNum);
}

void CX11WindowWM::SendRootMessage(TID pAtom,int p1,int p2,int p3,int p4,int p5)
{
    mWindow->SendClientMessage(mDisplay->GetDefaultRoot(),pAtom,p1,p2,p3,p4,p5);
}

bool CX11WindowWM::ShouldDecorate(CWindow *pWindow)
{
    switch(pWindow->WindowType())
    {
    case EWindowType::DESKTOP:
    case EWindowType::DOCK:
    case EWindowType::MENU:
    case EWindowType::SPLASH:
    case EWindowType::POPUPMENU:
    case EWindowType::TOOLTIP:
    case EWindowType::NOTIFICATION:
    case EWindowType::COMBO:
    case EWindowType::DND:
        return false;
    default:
        return true;
    }
}

bool CX11WindowWM::ShouldSkipTaskbar(CWindow *pWindow)
{
    switch(pWindow->WindowType())
    {
    case EWindowType::NORMAL:
        return false;
    default:
        return true;
    }
}

bool CX11WindowWM::ShouldSkipPager(CWindow *pWindow)
{
    switch(pWindow->WindowType())
    {
    case EWindowType::NORMAL:
        return false;
    default:
        return true;
    }
}

/*namespace x11*/ } /*namespace gui*/ } /*namespace buola*/ }
